package org.xerial.silk.editor.plugin.text;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.dltk.ui.text.AbstractScriptScanner;
import org.eclipse.dltk.ui.text.IColorManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;

public class SilkNodeScanner extends AbstractScriptScanner
{

    public SilkNodeScanner(IColorManager manager, IPreferenceStore store) {
        super(manager, store);
        initialize();
    }

    private static String fgTokenProperties[] = new String[] { SilkColor.Default.key, SilkColor.Number.key,
            SilkColor.String.key, SilkColor.NodeName.key, SilkColor.NodeValue.key, SilkColor.AttributeName.key,
            SilkColor.AttributeValue.key     };

    @Override
    protected String[] getTokenProperties() {
        return fgTokenProperties;
    }

    public final static String[] SILK_OPERATORS = { "|", "*", ">", "-" };

    @Override
    protected List<IRule> createRules() {

        // Create tokens
        IToken nodeNameToken = getToken(SilkColor.NodeName.key);
        IToken nodeValueToken = getToken(SilkColor.NodeValue.key);
        IToken numberToken = getToken(SilkColor.Number.key);
        IToken defaultToken = getToken(SilkColor.Default.key);
        IToken stringToken = getToken(SilkColor.String.key);

        // Create and populate list
        List<IRule> ruleList = new ArrayList<IRule>();

        // Create rules for detecting numbers and strings 
        ruleList.add(new SilkNumberRule(numberToken));
        ruleList.add(new SingleLineRule("\"", "\"", stringToken, '\\'));

        // Create a rule to detect nodes and attributes
        ruleList.add(new SilkQNameRule(nodeNameToken));
        //ruleList.add(new NodeNameRule(nodeNameToken));

        // Set the token returned for default text
        setDefaultReturnToken(defaultToken);

        return ruleList;
    }

    public static class Scanner
    {
        private final ICharacterScanner scanner;
        private int                     readCount = 0;

        public Scanner(ICharacterScanner scanner) {
            this.scanner = scanner;
        }

        public char peekNext() {
            char ch = (char) scanner.read();
            scanner.unread();
            return ch;
        }

        public char readNext() {
            char ch = (char) scanner.read();
            readCount++;
            return ch;
        }

        public void rollback() {
            for (; readCount > 0; readCount--) {
                scanner.unread();
            }
        }

    }

    public static class SilkNodeValueRule implements IRule
    {
        private final IToken targetToken;

        public SilkNodeValueRule(IToken targetToken) {
            this.targetToken = targetToken;
        }

        @Override
        public IToken evaluate(ICharacterScanner cs) {
            Scanner scanner = new Scanner(cs);
            char ch = scanner.readNext();
            if (ch == ':')

                scanner.rollback();
            return Token.UNDEFINED;
        }
    }

    public static class SilkQNameRule implements IRule
    {
        private final IToken targetToken;

        public SilkQNameRule(IToken targetToken) {
            this.targetToken = targetToken;
        }

        public static boolean isQNameFirstChar(char ch) {
            return (Character.isLetter(ch) || ch == '.' || ch == '_');
        }

        public static boolean isQNameChar(char ch) {
            return isQNameFirstChar(ch) || Character.isDigit(ch);
        }

        @Override
        public IToken evaluate(ICharacterScanner cs) {

            Scanner scanner = new Scanner(cs);
            char ch = scanner.readNext();
            if (isQNameFirstChar(ch)) {
                for (; isQNameChar((ch = scanner.peekNext())); scanner.readNext()) {}
                return targetToken;
            }
            scanner.rollback();
            return Token.UNDEFINED;
        }
    }

    public static class SilkNumberRule implements IRule
    {

        private IToken targetToken;

        public SilkNumberRule(IToken token) {
            this.targetToken = token;
        }

        @Override
        public IToken evaluate(ICharacterScanner cs) {

            Scanner scanner = new Scanner(cs);
            char c = scanner.readNext();
            boolean foundNumberPrefix = false;
            // ('-')? [0-9]+ ('.' [0-9]+)
            if (Character.isDigit(c)) {
                foundNumberPrefix = true;
            }
            else if (c == '-') {
                c = scanner.readNext();
                foundNumberPrefix = Character.isDigit(c);
            }

            if (foundNumberPrefix) {
                int numDot = 0;
                for (;; scanner.readNext()) {
                    c = scanner.peekNext();
                    if (Character.isDigit(c)) {
                        continue;
                    }
                    if (c == '.' && numDot == 0) {
                        numDot++;
                        continue;
                    }
                    return targetToken;
                }
            }

            scanner.rollback();
            return Token.UNDEFINED;
        }

    }

    public static class NodeNameRule implements IRule
    {

        private IToken nodeNameToken;

        public NodeNameRule(IToken nodeNameToken) {
            this.nodeNameToken = nodeNameToken;
        }

        public static boolean isNameChar(int ch) {
            return Character.isLetter(ch) || Character.isDigit(ch) || Character.isWhitespace(ch) || ch == '.'
                    || ch == '_';
        }

        @Override
        public IToken evaluate(ICharacterScanner scanner) {

            int ch = scanner.read();
            int readCount = 1;
            if (ch == '-') {
                do {
                    ch = scanner.read();
                    readCount++;
                }
                while (isNameChar(ch));

                scanner.unread();
                return nodeNameToken;
            }

            resetScanner(scanner, readCount);
            return Token.UNDEFINED;
        }

        public static void resetScanner(ICharacterScanner scanner, int readCount) {
            while (readCount > 0) {
                readCount--;
                scanner.unread();
            }
        }

    }

}
